package com.shiguiwu.springmybatis.javabase.thread.lock.impl;

import cn.hutool.core.util.StrUtil;
import com.shiguiwu.springmybatis.javabase.thread.lock.RedisLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @description: 实现锁续命
 * <p>
 * redis可重入自旋锁实现异步续命，防止超时的情况出现，锁被夺走
 * 实现原理：增加一个线程，异步更新该锁的失效时间，直到加锁的线程将锁释放
 * @author: stone
 * @date: Created by 2022/1/15 20:46
 * @version: 1.0.0
 * @pakeage: com.shiguiwu.springmybatis.lock.impl
 */
@Component
@Slf4j
public class RedisLockAsynTime implements RedisLock {


    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    //加锁的次数
    private static ThreadLocal<Integer> lOCK_TIMES = new ThreadLocal<>();

    //加锁的key
    private static ThreadLocal<String> LOCAL_UUID = new ThreadLocal<>();

    //自旋锁的等待时间，1000后再次尝试获取锁
    private static final long REENTRY_SPIN_SLEEP = 10;

    @Override
    public boolean tryLock(String lockKey, long timeout, TimeUnit unit) {
        boolean isLock = false;
        String value = LOCAL_UUID.get();
        if (StrUtil.isBlank(value)) {
            value = UUID.randomUUID().toString();
            LOCAL_UUID.set(value);
            while (true) {
                //尝试上锁
                isLock = redisTemplate.opsForValue().setIfAbsent(lockKey, value, timeout, unit);
                //如果成功，就跳出循环
                if (isLock) {
                    break;
                }
                try {
                    TimeUnit.SECONDS.sleep(REENTRY_SPIN_SLEEP);
                } catch (InterruptedException e) {
                    log.error("thread sleep err .......");
                    e.printStackTrace();
                }

            }
            //设置锁的次数是 0
            lOCK_TIMES.set(0);
            //开启一个续命程序
            new Thread(new AddTimeForLock(lockKey, value, redisTemplate)).start();
        }
        //改线程已经上锁
        else {
            isLock = true;
        }
        Integer time = lOCK_TIMES.get();
        if (isLock) {
            lOCK_TIMES.set(time + 1);
        }
        return isLock;
    }

    @Override
    public void releaseLock(String lockKey) {
        String v = LOCAL_UUID.get();
        String value = redisTemplate.opsForValue().get(lockKey);
        if (StrUtil.isNotBlank(v) && v.equalsIgnoreCase(value)) {
            Integer time = lOCK_TIMES.get();
            //如果已经是本线程，并且已经上锁,锁数量大于0
            if (null != time && time > 0) {
                lOCK_TIMES.set(time - 1);
            } else {
                //计数器减为0则解锁
                redisTemplate.delete(lockKey);
                LOCAL_UUID.remove();
                lOCK_TIMES.remove();
            }
        }
    }


}
